<?php
namespace Yls\Core;

use Yls;
use PDO;
use Exception;

/**
 * Yls\Core\Db
 * 数据库操作类
 * 
 * @author keluo <keluo@focrs.com>
 * @copyright 2017 Focrs, Co.,Ltd
 * @package Yls
 * @since 2015-02-02
 */
class Db {
    
    protected $_connect = null;
    protected $_table = null;
    protected $_semantics;
    protected $_cache = 0; // 缓存时间(秒)
    
    const FETCH_ASSOC = PDO::FETCH_ASSOC; // 返回关联数组
    const FETCH_OBJECT = PDO::FETCH_OBJ; // 返回对象属性
    const FETCH_COLUMN = PDO::FETCH_COLUMN; // 返回第一列
    
    const LOCK_X = 1; // 排他锁(FOR UPDATE)
    const LOCK_S = 2; // 共享锁(LOCK IN SHARE MODE)
    
    /**
     * 初始化
     * @param string $connect 连接标识，默认default
     */
    public function __construct($connect = 'default') {
        $this->_connect = $connect ? $connect : 'default';
        $this->_semantics = new \stdClass();
    }
    
    /**
     * 切换数据库连接
     * @param string $connect
     * @return \Yls\Core\Db
     */
    public function Connect($connect) {
        $this->_connect = $connect;
        return $this->Reset();
    }
    
    /**
     * 重置SQL语义树
     * @return \Yls\Core\Db
     */
    public function Reset() {
        $this->_semantics = new \stdClass();
        $this->_table = null;
        return $this;
    }
    
    /**
     * 需要查询的信息
     * @param string $selector
 *      [ALL | DISTINCT | DISTINCTROW ]
        [HIGH_PRIORITY]
        [STRAIGHT_JOIN]
        [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
        [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
        select_expr, ...
        [INTO OUTFILE 'file_name' export_options|INTO DUMPFILE 'file_name']
       @see https://dev.mysql.com/doc/refman/5.7/en/select.html
     * @return \Yls\Core\Db
     * @example
     *  Yls::Db()->Select('~User.id, {User}.name')->From('~User');
     *  Yls::Db()->Select('*')->From('~User');
     *  Yls::Db()->Select('COUNT(*), MAX(id), MIN(id)')->From('~User');
     */
    public function Select($selector = '*') {
        $this->_semantics->selector = trim(trim($selector), ',');
        return $this;
    }
    
    /**
     * 选择数据表
     * @param string $table table_references
     * @return \Yls\Core\Db
     * @example
     *  Yls::Db()->From('~User');
     *  Yls::Db()->From('~User AS u'); // 别名
     *  Yls::Db()->From('~User, Message'); // 多表
     *  Yls::Db()->From('~User USE INDEX (key_name)'); // 声明使用索引key_name优化查询
     */
    public function From($table) {
        $this->_table = $table;
        return $this;
    }
    
    /**
     * 指定分区
     * @param string $name 分区名
     * @example
     *  Yls::Db()->From('~User')->Partition('user0'); // 指定从User分区表user0中查询
     */
    public function Partition($name) {
        $this->_semantics->partition = $name;
        return $this;
    }
    
    /**
     * 连接数据表
     * @param string $type 连接方式
     *  INNER
     *  | CROSS
     *  | STRAIGHT_JOIN
     *  | LEFT [OUTER]
     *  | NATURAL [LEFT [OUTER]]
     *  | RIGHT [OUTER]
     *  | NATURAL [RIGHT [OUTER]]
     * @param string $table table_references
     * @param string $op ON|USING
     * @param string $condition 连接条件
     * @example 
     * Yls::Db()->From('~User')->Join('lEFT', '~Message', 'ON', '~User.id = ~Message.userId');
     * Yls::Db()->From('~UserInfo')->Join('lEFT', '~Message', 'USING', 'userId');
     */
    public function Join($type, $table, $op = null, $condition = null) {
        $this->_semantics->join[] = [$type, $table, $op, $condition];
        return $this;
    }
    
    /**
     * SQL WHERE条件
     * @param string $where
     * @example
     *  Yls::Db()->From('~User')->Where('name LIKE "%Yls%"');
     *  Yls::Db()->From('~User')->Where('id = 1 FOR UPDATE');
     */
    public function Where($where) {
        if(trim($where)) $this->_semantics->where = trim($where);
        return $this;
    }
    
    /**
     * SQL GROUP BY条件
     * @param string $column 需要分组的字段列
     * @param string $sort ASC|DESC 排序方式
     * @param boolean $rollup WITH ROLLUP 开启上卷查询
     * @example
     *  Yls::Db()->From('~User')->Group('type');
     *  Yls::Db()->From('~User')->Group('type', 'DESC');
     *  Yls::Db()->From('~User')->Group('type', null, true);
     */
    public function Group($column, $sort = null, $rollup = false) {
        $group = trim($column);
        if(strlen($group)) {
            if(strlen($sort)) $group .= ' '.strtoupper($sort);
            if($rollup) $group .= ' WITH ROLLUP';
            $this->_semantics->group = $group;
        }
        return $this;
    }
    
    /**
     * SQL HAVING条件
     * @param string $condition 条件
     * @example
     *  Yls::Db()->From('~User')->Group('type')->Having('age > 20');
     */
    public function Having($condition) {
        if(trim($condition)) $this->_semantics->having = trim($condition);
        return $this;
    }
    
    /**
     * SQL ORDER BY条件
     * @param string $order
     * @param string $sort ASC|DESC 排序方式
     * @example
     *  Yls::Db()->From('~User')->Order('age');
     *  Yls::Db()->From('~User')->Order('age', 'DESC')->Order('id');
     */
    public function Order($column, $sort = null) {
        $this->_semantics->order = (isset($this->_semantics->order) && strlen($this->_semantics->order)) 
            ? $this->_semantics->order.', '.trim($column) : trim($column);
        if(strlen($sort)) $this->_semantics->order .= ' '.strtoupper($sort);
        return $this;
    }
    
    /**
     * SQL LIMIT条件
     * @param integer $size 查询记录数
     * @param integer $offset 记录起始偏移量
     * @example
     *  Yls::Db()->From('~User')->Limit(10); // 从第一条记录开始取出10条
     *  Yls::Db()->From('~User')->Limit(10, 20); // 从第21条记录开始取出10条
     */
    public function Limit($size, $offset = 0) {
        $this->_semantics->limit = ($offset ? $offset.', ' : '').$size;
        return $this;
    }
    
    /** 
     * INTO OUTFILE 'file_name' [CHARACTER SET charset_name] export_options
        | INTO DUMPFILE 'file_name'
        | INTO var_name [, var_name]]
     * @param string $type OUTFILE|DUMPFILE|null
     * @param $option
     */
    public function Into($type, $option) {
        $this->_semantics->into = [$type, $option];
    }
    
    /**
     * 返回单条记录
     * @param const $lockType Db::LOCK_X|Db::LOCK_S 查询锁类型 
     * @param const $fetchType Db::FETCH_OBJECT|Db::FETCH_ASSOC|Db::FETCH_COLUMN 返回数据格式
     * @return array
     * @throws ErrorException
     */
    public function Read($lockType = null, $fetchType = self::FETCH_OBJECT) {
        return $this->Fetch($this->ConcatSelect(0, 1, $lockType), $fetchType, false);
    }
    
    /**
     * 返回多条查询记录
     * @param const $lockType Db::LOCK_X|Db::LOCK_S 查询锁类型 
     * @param const $fetchType Db::FETCH_OBJECT|Db::FETCH_ASSOC|Db::FETCH_COLUMN 返回数据格式
     * @return array
     * @throws ErrorException
     */
    public function ReadAll($lockType = null, $fetchType = self::FETCH_OBJECT) {
        return $this->FetchAll($this->ConcatSelect(0, null, $lockType), $fetchType);
    }
    
    /**
     * 返回指定页数据记录
     * @param number $offset 偏移量
     * @param number $size 每页记录数
     * @param const $lockType Db::LOCK_X|Db::LOCK_S 查询锁类型
     * @param const $fetchType Db::FETCH_OBJECT|Db::FETCH_ASSOC|Db::FETCH_COLUMN 返回数据格式
     * @return array 第一个元素返回记录结果集，第二个元素返回总记录数
     * @example list($list, $total) = Yls::Db()->From('User')->ReadPage(1, 20);
     */
    public function ReadPage($offset, $size, $lockType = null, $fetchType = self::FETCH_OBJECT) {
        $list = $this->FetchAll($this->ConcatSelect($offset, $size, $lockType), $fetchType); 
        $total = intval($this->Fetch('SELECT FOUND_ROWS()', self::FETCH_COLUMN));
        return [$list, $total];
    }
    
    /**
     * 从数据库中获取匹配数据第一条记录
     * @param mixed $sql Yls\Core\Condition|SELECT查询语句
     * @param const $fetchType Db::FETCH_OBJECT|Db::FETCH_ASSOC|Db::FETCH_COLUMN 获取方式
     * @return Ambigous <multitype:, unknown, mixed>
     */
    public function Fetch($sql, $fetchType = self::FETCH_OBJECT) {
        if($sql instanceof Yls\Core\Condition) {
            $sql = $this->ConcatCondition($sql, $fetchType);
        }
        $stmt = $this->Dbh()->prepare($this->Transform((string)$sql));
        $stmt->execute();
        return $stmt->fetch($fetchType) ? : false;
    }
    
    /**
     * 返回指定页数据记录
     * @param mixed $sql Yls\Core\Condition|SELECT查询语句
     * @param number $offset 偏移量
     * @param number $size 每页记录数
     * @param const $fetchType Db::FETCH_OBJECT|Db::FETCH_ASSOC|Db::FETCH_COLUMN 返回数据格式
     * @return array 第一个元素返回记录结果集，第二个元素返回总记录数
     * @example list($list, $total) = Yls::Db()->FetchPage('SELECT * FROM User WHERE id > 5', 1, 20);
     */
    public function FetchPage($sql, $offset = 0, $size = 10, $fetchType = self::FETCH_OBJECT) {
        if($sql instanceof Yls\Core\Condition) {
            $Condition = $sql;
            $sql = $this->ConcatCondition($Condition, $fetchType, true);
            if($Condition->size) $size = $Condition->size;
            if($Condition->offset) $offset = $Condition->offset;
        }
        
        $sql = preg_replace(['/(?<=SELECT\s)(.*)(?=\sFROM)/is', '/\sLIMIT\s.*/is'], ['\1', ''], $sql);
        $sql .= ' LIMIT '.$offset.', '.$size;
        
        $list = $this->FetchAll($sql, $fetchType);
        $total = intval($this->Fetch('SELECT FOUND_ROWS()', self::FETCH_COLUMN));
        return [$list, $total];
    }
    
    /**
     * 从数据库中获取所有匹配记录
     * @param mixed $sql Yls\Core\Condition|SELECT查询语句
     * @param const $fetchType Db::FETCH_OBJECT|Db::FETCH_ASSOC|Db::FETCH_COLUMN 获取方式
     * @return Ambigous <multitype:, unknown, mixed>
     */
    public function FetchAll($sql, $fetchType = self::FETCH_OBJECT) {
        if($sql instanceof \Yls\Core\Condition) {
            $sql = $this->ConcatCondition($sql, $fetchType, true);
        }
        $stmt = $this->Dbh()->prepare($this->Transform((string)$sql));
        $stmt->execute();
        $data = $stmt->fetchAll($fetchType);
        return is_array($data) ? $data : [];
    }
    
    /**
     * 插入数据
     * @param array $data 一维或二维数组
     * @param boolean $replace 主键重复则替换
     * @return integer 最后一条记录主键ID
     * @example
     *  Yls::Db()->From('~User')->Insert(['name' => 'Yls', 'password' => md5('Y-e-s!')]); // 单条
     *  Yls::Db()->From('~User')->Insert([ // 多条
     *      ['name' => 'Yls', 'password' => md5('Y-e-s!')],
     *      ['name' => 'Focrs', 'password' => md5('Focus Fox')],
     *      ...
     *  ]);
     */
    public function Insert($data = null, $replace = false) {
        $attrs = [0 => null];
        if(null === $data) {
            $attrs[0] = Yls::Instance()->Param();
        } elseif(is_array($data)) {
            if(!isset($data[0])) {
                $attrs[0] = $data;
            } else {
                $attrs = $data;
            }
        }
        if(!$attrs[0]) throw new Exception('Insert data is empty');
        $fields = [];
        foreach($attrs as $a) {
            $fields = array_unique(array_merge($fields, array_keys($a)));
        }
        $columns = $this->GetTableStruct($this->_table, 'fields');
        $fields = array_intersect($fields, $columns);
        
        $connect = Yls::Instance()->db[$this->_connect];
        if(isset($connect['create']) && $connect['create']) {
            if(!strpos($connect['create'], ':')) $connect['create'] .= ':datetime';
            list($field, $type) = explode(':', $connect['create']);
            if(!in_array($field, $fields)) {
                $fields[] = $field;
                $autoCreate = [$field, $type];
            }
        }
        if(isset($connect['update']) && $connect['update']) {
            if(!strpos($connect['update'], ':')) $connect['update'] .= ':datetime';
            list($field, $type) = explode(':', $connect['update']);
            if(!in_array($field, $fields)) {
                $fields[] = $field;
                $autoUpdate = [$field, $type];
            }
        }
        $values = [];
        foreach($attrs as $attr) {
            $value = '';
            foreach($fields as $field) {
                if(array_key_exists($field, $attr) && !is_array($attr[$field]) && !is_object($attr[$field])) {
                    if(preg_match('/^\{.*\}$/s', $attr[$field])) { // 特殊处理标识,如'hits' => '{hits + 1}'
                        $value .= $this->Escape(substr($attr[$field], 1, -1)).', ';
                    } else {
                        $value .= is_null($attr[$field]) ? 'NULL, ' : '"'.$this->Escape($attr[$field]).'", ';
                    }
                } elseif(isset($autoCreate)) {
                    $value .= ('datetime' == $autoCreate[1] ? '"'.date('Y-m-d H:i:s').'"' : time()).', ';
                } elseif(isset($autoUpdate)) {
                    $value .= ('datetime' == $autoUpdate[1] ? '"'.date('Y-m-d H:i:s').'"' : time()).', ';
                } else {
                    $value .= 'NULL, ';
                }
            }
            
            $values[] = '('.rtrim($value, ', ').')';
        }
        $sql = ($replace ? 'REPLACE' : 'INSERT').' INTO `'.trim($this->Observe($this->_table), '`').'`';
        $sql .= ' (`'.implode('`, `', $fields).'`)';
        $sql .= ' VALUES '.implode(', ', $values);
        $stmt = $this->Dbh()->prepare($sql);
        $stmt->execute();
        return $this->Dbh()->lastInsertId();
    }
    
    /**
     * 更新数据
     * @param array $data
     * @param boolean $whole 是否整表更新(该参数作用是防止未加WHERE条件引起的灾难性后果)
     * @return boolean
     * @example
     *  Yls::Instance()->From('~User')->Where('id = 1')->Update(['name' => 'Yls'], ['hits' => '{hits + 1}']);
     *  Yls::Instance()->From('~User')->Order('id DESC')->Limit(1)->Update(['name' => 'Yls']);
     */
    public function Update($data = null, $whole = false) {
        if(null === $data) $data = Yls::Instance()->Param();
        $columns = $this->GetTableStruct($this->_table, 'fields');
        $sets = [];
        foreach($data as $field=>$value) {
            if(!in_array($field, $columns)) continue;
            if(is_array($value) || is_object($value)) continue;
            if(preg_match('/^\{.*\}$/s', $value)) { // 特殊处理标识,如'count' => '{count + 1}'
                $sets[] = '`'.$field.'` = '.substr($this->escape($value), 1, -1);
            } else {
                $sets[] = '`'.$field.'` = '.(is_null($value) ? 'NULL' : ('"'.$this->escape($value)).'"');
            }
        }
        $connect = Yls::Instance()->db[$this->_connect];
        if(isset($connect['update']) && $connect['update']) {
            if(!strpos($connect['update'], ':')) $connect['update'] .= ':datetime';
            list($field, $type) = explode(':', $connect['update']);
            if(!array_key_exists($field, $data) && in_array($field, $columns)) {
                $sets[] = '`'.$field.'` = '.('datetime' == $type ? '"'.date('Y-m-d H:i:s').'"' : time());    
            }
        }
        if(empty($sets)) return 0;
        $sql = 'UPDATE `'.$this->Observe($this->_table).'` SET '.implode(', ', $sets);
        if(!isset($this->_semantics->where) && !$whole) { // 不允许整表更新，需强制加条件，防止误更新
            throw new Exception('Update the whole table is not allowed for security [SQL: '.$sql.']');
        } else {
            $sql .= ' WHERE '.$this->_semantics->where;
        }
        $sql .= isset($this->_semantics->order) && strlen($this->_semantics->order) ? ' ORDER BY '.$this->_semantics->order : '';
        $sql .= isset($this->_semantics->limit) && strlen($this->_semantics->limit) ? ' LIMIT '.$this->_semantics->limit : '';
        
        $stmt = $this->Dbh()->prepare($sql);
        return $stmt->execute();
    }
    
    /**
     * 删除数据
     * @param boolean $whole 是否删除整张表(该参数作用是防止未加WHERE条件引起的灾难性后果)
     * @return boolean
     */
    public function Delete($whole = false) {
        $sql = 'DELETE FROM `'.$this->Observe($this->_table).'`';
        if(!isset($this->_semantics->where) && !$whole) { // 不允许整表删除，需强制加条件，防止误删
            throw new Exception('Delete the whole table is not allowed for security [SQL: '.$sql.']');
        }
        $sql .= isset($this->_semantics->where) && strlen($this->_semantics->where) ? (' WHERE '.$this->_semantics->where) : '';
        $sql .= isset($this->_semantics->order) && strlen($this->_semantics->order) ? ' ORDER BY '.$this->_semantics->order : '';
        $sql .= isset($this->_semantics->limit) && strlen($this->_semantics->limit) ? ' LIMIT '.$this->_semantics->limit : '';
        
        $stmt = $this->Dbh()->prepare($this->Transform($sql));
        return $stmt->execute();
    }
    
    /**
     * 执行SQL语句
     * @param string $sql SQL语句
     * @return 受影响记录数
     */
    public function Execute($sql) {
        try {
            $stmt = $this->Dbh()->prepare($sql);
            return $stmt->execute();
        } catch(Exception $e) {
            throw new Exception($e->getMessage().' [SQL:'.$sql.']');
        }
    }
    
    /**
     * 清空表
     */
    public function Truncate() {
        return $this->Execute('Trancate Table `'.$this->Observe($this->_table).'`');
    }
    
    /**
     * 返回最后一条插入数据的主键ID
     */
    public function LastInsertId() {
        return $this->Dbh()->lastInsertId();
    }
    
    /**
     * 开始事务处理
     */
    public function BeginTransaction() {
        if($this->Dbh()->inTransaction()) return;
        $this->Dbh()->setAttribute(PDO::ATTR_AUTOCOMMIT, FALSE);
        $this->Dbh()->beginTransaction();
    }
    
    /**
     * 撤销事务
     */
    public function Rollback() {
        $this->Dbh()->rollBack();
    }
    
    /**
     * 提交事务
     */
    public function Commit() {
        $this->Dbh()->commit();
    }
    
    /**
     * 获取表结构信息
     * @param string $table 表名
     * @param string $type fields|pk 所有字段或主键字段
     * @return array
     */
    public function GetTableStruct($table, $type = null) {
        static $structs = [];
        $table = $this->Observe($table);
        $flag = ($this->_connect ? $this->_connect.'.' : '').$table;
        if(!isset($structs[$flag])) {
            $shows = $this->FetchAll('SHOW COLUMNS FROM `'.trim($table, '`').'`', self::FETCH_OBJECT, true);
            $fields = [];
            $pk = null;
            foreach($shows as $k=>$v) {
                $fields[] = $v->Field;
                if('PRI' == $v->Key && 'auto_increment' == $v->Extra) $pk = $v->Field;
            }
            $structs[$flag] = compact('pk', 'fields');
        }
        return $type ? $structs[$flag][$type] : $structs[$flag];
    }
    
    /**
     * 将SQL中简写形式转换为对应的数据库名和数据表名
     * 两个前缀~~紧邻出现，后面的字符串将被替换为对应的数据库名；仅一个~出现，后面的字符串将被替换为对应连接配置的表前缀
     * @param string $sql
     * @example
     * 配置config中default 配置如下：
     * ...
     * 'default => [
     *   'dsn' => 'mysql:host=127.0.0.1;dbname=yls;charset=utf8',
     *   'prefix' => 'Y',
     *   ...
     * ],
     * 'buz' => [
     *   'dsn' => 'mysql:host=127.0.0.1;dbname=business;charset=utf8',
     *   'prefix' => 'F',
     * ]
     * 
     * SELECT ~User.id FROM ~User; // 转换为：Select YUser.id FROM YUser;
     * SELECT user.userId FROM ~~buz.~User AS user LEFT JOIN ~User ON ~User.id = user.userId;
     * // 转换为：SELECT user.userId FROM business.FUser AS user LEFT JOIN YUser ON YUser.id = user.userId;
     */
    private function Transform($sql) {
        $length = strlen($sql);
        $slices = $segments = [];
        for($i = 0; $i < $length; $i++) {
            if(in_array($sql[$i], ['\'', '"']) && !(isset($sql[$i-1]) && $sql[$i-1] == '\\')) {
                $last = end($slices);
                if(!$last) {
                    array_push($slices, [$i, $sql[$i], false]);
                } else {
                    if($last[1] == $sql[$i]) {
                        array_push($slices, [$i, $sql[$i], !$last[2]]);
                    } elseif($last[2]) {
                        array_push($slices, [$i, $sql[$i], false]);
                    }
                }
            }
        }
        
        if($slices) {
            foreach($slices as $k=>$slice) {
                if($k%2 == 0) $segments[] = [$slice[0], $slices[$k+1][0], $slice[1]];
            }
        } else {
            $segments[] = [0, $length - 1, true];
        }
        $slices = [];
        if($segments[0][0] > 0) array_push($slices, [0, $segments[0][0] - 1, true]);
        for($i = 0; $i < count($segments); $i++) {
            array_push($slices, [$segments[$i][0], $segments[$i][1], $segments[$i][2]]);
            if(isset($segments[$i+1]) && $segments[$i+1][0] > $segments[$i][1]) {
                array_push($slices, [$segments[$i][1] + 1, $segments[$i+1][0] - 1, true]);
            }
        }
        if(end($slices)[1] < $length - 1) array_push($slices, [end($slices)[1] + 1, $length - 1, true]);
        $return = '';
        foreach($slices as $slice) {
            $fragment = substr($sql, $slice[0], $slice[1] - $slice[0] + 1);
            $return .= $slice[2] ? $this->Observe($fragment) : $fragment;
        }
        return $return;
    }
    
    /**
     * 观察并替换SQL(片段)中的~前缀
     * @param string $fragment SQL片段
     */
    public function Observe($fragment) {
        return preg_replace_callback('/~(~?(\w+)((\.~?\w+)(\.\w+)?)?)/', function($match) {
            $count = substr_count($match[0], '.');
            if($count == 0) {
                return Yls::Instance()->db[$this->_connect]['prefix'].trim($match[0], '~');
            } elseif($count == 1) {
                list($left, $right) = explode('.', $match[0]);
                if(preg_match('/^~{2}/', $left)) {
                    $config = Yls::Instance()->db[trim($left, '~')];
                    preg_match('/dbname=(\w+);?/', $config['dsn'], $m);
                    if(0 === strpos($right, '~')) {
                        return $m[1].'.'.$config['prefix'].trim($right, '~');
                    } else {
                        return $m[1].'.'.$right;
                    }
                } else {
                    return Yls::Instance()->db[$this->_connect]['prefix'].trim($left, '~').'.'.$right;
                }
            } elseif($count == 2) {
                list($connect, $table, $field) = explode('.', $match[0]);
                $config = Yls::Instance()->db[trim($connect, '~')];
                if(0 === strpos($table, '~')) $table = $config['prefix'].trim($table, '~');
                preg_match('/dbname=(\w+);?/', $config['dsn'], $m);
                return implode('.', [$m[1], $table, $field]);
            }
        }, $fragment);
    }
    
    /**
     * 获取到数据库的连接
     */
    private function Dbh() {
        static $connectors = [];
        if(!isset($connectors[$this->_connect])) {
            $config = Yls::Instance()->db[$this->_connect];
            $connectors[$this->_connect] = new PDO($config['dsn'], $config['username'], $config['password'], [PDO::ATTR_PERSISTENT => false]);
            $connectors[$this->_connect]->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 设置异常抛出方式
        }
        return $connectors[$this->_connect];
    }
    
    /**
     * 拼装SELECT语句
     * @param integer $offset 偏移量
     * @param integer $size 获取记录数
     * @param const $fetchType Db::FETCH_OBJECT|Db::FETCH_ASSOC|Db::FETCH_COLUMN 获取方式
     * @param boolean $calFoundRows 计算匹配结果总数
     * @return string SQL
     */
    private function ConcatSelect($offset, $size, $lockType = null, $calFoundRows = false) {
        $sql = 'SELECT'.($calFoundRows ? ' SQL_CALC_FOUND_ROWS' : '');
        $sql .= isset($this->_semantics->selector) ? ' '.$this->_semantics->selector : ' *';
        $sql .= ' FROM '.$this->_table;
        if(isset($this->_semantics->join)) {
            foreach($this->_semantics->join as $v) {
                $sql .= ' '.$v[0].' JOIN '.$v[1];
                if(isset($v[2])) $sql .= ' '.$v[2];
                if(isset($v[3])) $sql .= ' '.$v[3];
            }
        }
        $sql .= isset($this->_semantics->where) && strlen($this->_semantics->where) ? ' WHERE '.$this->_semantics->where : '';
        $sql .= isset($this->_semantics->group) && strlen($this->_semantics->group) ? ' GROUP BY '.$this->_semantics->group : '';
        $sql .= isset($this->_semantics->having) && strlen($this->_semantics->having) ? ' HAVING '.$this->_semantics->having : '';
        $sql .= isset($this->_semantics->order) && strlen($this->_semantics->order) ? ' ORDER BY '.$this->_semantics->order : '';
        $sql .= ($size > 0) ? ' LIMIT '.($offset ? $offset.', ' : '').$size : '';
        
        if($lockType && in_array($lockType, [self::LOCK_X, self::LOCK_S])) {
            $sql .= $lockType == self::LOCK_X ? ' FOR UPDATE' : ' LOCK IN SHARE MODE';
        }
        return $sql;
    }
    
    /**
     * 拼装SELECT语句
     * @param $Condition Yls\Core\Condition
     * @param const $fetchType Db::FETCH_OBJECT|Db::FETCH_ASSOC|Db::FETCH_COLUMN 获取方式
     * @param boolean $calFoundRows 计算匹配结果总数
     * @return string SQL
     * @example
     * Yls::Db()->From('~User')->FetchAll(new Yls\Core\Condition(...));
     * 
     */
    private function ConcatCondition($Condition, $fetchType = null, $calFoundRows = false) {
        $sql = 'SELECT'.($calFoundRows ? ' SQL_CALC_FOUND_ROWS' : '');
        $sql .= $Condition->select ? ' '.$Condition->select : ' *';
        $sql .= ' FROM '.$this->_table;
        if(isset($this->_semantics->join)) {
            foreach($this->_semantics->join as $v) {
                $sql .= ' '.$v[0].' JOIN '.$v[1];
                if(isset($v[2])) $sql .= ' '.$v[2];
                if(isset($v[3])) $sql .= ' '.$v[3];
            }
        }
        $sql .= $Condition->where ? ' WHERE '.$Condition->where : '';
        $sql .= $Condition->group ? ' GROUP BY '.$Condition->group : '';
        $sql .= $Condition->having ? ' HAVING '.$Condition->having : '';
        $sql .= $Condition->order ? ' ORDER BY '.$Condition->order : '';
        $sql .= ($Condition->size > 0) ? ' LIMIT '.($Condition->offset ? $Condition->offset.', ' : '').$Condition->size : '';
        
        if($Condition->lock && in_array($Condition->lock, [self::LOCK_X, self::LOCK_S])) {
            $sql .= $Condition->lock == self::LOCK_X ? ' FOR UPDATE' : ' LOCK IN SHARE MODE';
        }
        return $sql;
    }
     
    /**
     * 对入库数据进行安全转义
     * @param string $str
     * @return string
     */
    private function Escape($str) {
        if(get_magic_quotes_gpc()) $str = stripslashes($str);
        return get_magic_quotes_runtime() ? trim($str) : addslashes(trim($str));
    }
}